﻿#include "ClientFileSocket.h"
#include "MyApp.h"
#include "FileMsg.h"
#include "protocolstream.h"
#include "unit.h"
#include "global.h"
#include "CIULog.h"

#include <QDebug>
#include <QTcpSocket>
#include <QFile>
#include <QThread>
#include <QHostAddress>

int ClientFileSocket::m_sWaitForTimeout = 300000;
ClientFileSocket::ClientFileSocket(QObject *parent)
    : QObject(parent),
      m_seq(0),
      m_nSendStop(true),
      m_nRecvStop(true)
{
    m_pTcpSocket = new QTcpSocket(this);
    m_pTcpSocket->setSocketOption(QAbstractSocket::LowDelayOption,1);
    connect(m_pTcpSocket, static_cast<void(QTcpSocket::*)(QTcpSocket::SocketError)>(&QAbstractSocket::error),
          [=](QAbstractSocket::SocketError socketError){
        LOG_ERROR("DisConnect from host %s:%d , error : %s",
                  m_pTcpSocket->localAddress().toString().toStdString().c_str(),
                  m_pTcpSocket->localPort(),
                  m_pTcpSocket->errorString().toStdString().c_str());
    });
}

ClientFileSocket::~ClientFileSocket()
{
    LOG_INFO("");
}

bool ClientFileSocket::IsConneciton()
{
    return m_pTcpSocket->isOpen();
}

void ClientFileSocket::StopSend()
{
    m_nSendStop = true;
}

void ClientFileSocket::StopRecv()
{
    m_nRecvStop = true;
}

//连接服务器
void ClientFileSocket::ConnectToHost()
{
    if (m_pTcpSocket->isOpen())
    {
        m_pTcpSocket->abort();
    }

    m_pTcpSocket->connectToHost(MyApp::m_strFileServerAddr,MyApp::m_nFileSeverPort);
}

void ClientFileSocket::CloseFileServerConnection()
{
    m_pTcpSocket->abort();
}

void ClientFileSocket::SltUploadFile(QString fileName, QString fileMd5)
{
    LOG_INFO("");
    // 如果没有连接服务器，重新连接下
    if (!m_pTcpSocket->isOpen()) {
        m_pTcpSocket->connectToHost(MyApp::m_strFileServerAddr,static_cast<quint16>(MyApp::m_nFileSeverPort) );
        if(!m_pTcpSocket->waitForConnected(m_sWaitForTimeout))
        {
            LOG_INFO( "Connect to host %s:%d",
                      m_pTcpSocket->localAddress().toString().toStdString().c_str(),
                      m_pTcpSocket->localPort() );
            Q_EMIT sigTransFileStatus(FILE_UPLOAD_FAILED);
            return;
        }
    }

    QFile fileToSend(fileName);

    if (!fileToSend.open(QFile::ReadOnly))
    {
        LOG_ERROR( "Open file error : %s",fileToSend.errorString().toStdString().c_str() );
        Q_EMIT sigTransFileStatus(FILE_UPLOAD_FAILED);
        return;
    }

    // 文件总大小
    int SendTotalBytes = static_cast<int>(fileToSend.size());
    //现在只支持4g大小的文件，如果超过4g，转换成nFileSize会溢出
    if (SendTotalBytes != static_cast<int>(fileToSend.size()))
    {
        LOG_ERROR("Failed to upload file:%s as file is too big.",fileName.toStdString().c_str());
        Q_EMIT sigTransFileStatus(FILE_UPLOAD_FAILED);
        return;
    }

    qint8 nBreakType = FILE_UPLOAD_FAILED;
    int offset = 0;
    int eachfilesize = 512* 1024;
    QByteArray outblock;
    QByteArray recvBuf;
    qint64 datalength = 0;         //收到数据包的去除包头的数据大小
    m_nSendStop = false;
    while(!m_nSendStop)
    {
        outblock.resize(0);
        std::string outbuf;
        yt::BinaryWriteStream3 writeStream(&outbuf);
        writeStream.Write(msg_type_upload_req);
        writeStream.Write(m_seq);
        writeStream.Write(fileMd5.toStdString().c_str(), 32);
        writeStream.Write(static_cast<int>(offset));         //Fixme:qint64转int不安全
        writeStream.Write(static_cast<int>(SendTotalBytes));
        if (SendTotalBytes - offset < eachfilesize)
            eachfilesize = SendTotalBytes - offset;
        outblock = fileToSend.read(eachfilesize);
        writeStream.Write(outblock.constData(), static_cast<size_t>(outblock.length()));
        writeStream.Flush();

        file_msg headerx = { outbuf.length() };
        outbuf.insert(0, (const char*)&headerx, sizeof(headerx));
        if (!SendPackage(outbuf.c_str(), outbuf.length()))
        {
            break;
        }

        offset += eachfilesize;
        //通知界面更改进度条
        if(SendTotalBytes != 0)
        {
            Q_EMIT sigUpdateTransFileProgress(static_cast<int>((static_cast<qint64>(offset)* 100) / SendTotalBytes));
        }
        //Q_EMIT sigUpdateTransFileProgress(static_cast<int>((offset/(float)SendTotalBytes)*100));
        qDebug() << offset  << eachfilesize;
        recvBuf = recvBuf.mid(static_cast<int>(datalength));      //FIXME:去除包体数据,qint64->int会不会有问题
        //取出一个数据包
        while(true)
        {
            if(!m_pTcpSocket->waitForReadyRead(m_sWaitForTimeout))
            {
                //网络问题
                Q_EMIT sigTransFileStatus(FILE_UPLOAD_FAILED);
                return;
            }
            QByteArray buffer = m_pTcpSocket->readAll();
            recvBuf.append(buffer);
            //不够一个包头大小
            if (recvBuf.size() < sizeof(file_msg))
            {
                continue;
            }
            //不够一个整包大小
            file_msg header;
            memcpy(&header, recvBuf.constData(), sizeof(file_msg));
            datalength = header.packagesize;
            if (recvBuf.size() < datalength + sizeof(file_msg))
                continue;
            //去除包头
            recvBuf = recvBuf.mid(sizeof(file_msg));
            break;
        }

        yt::BinaryReadStream2 readStream(recvBuf.constData(),recvBuf.size());
        int cmd;
        if (!readStream.Read(cmd) || cmd != msg_type_upload_resp)
        {
            break;
        }

        //int seq;
        if (!readStream.Read(m_seq))
        {
            break;
        }

        std::string filemd5;
        size_t md5length;
        if (!readStream.Read(&filemd5, 0, md5length) || md5length != 32)
        {
            break;
        }

        int offset;
        if (!readStream.Read(offset))
        {
            break;
        }

        int filesize;
        if (!readStream.Read(filesize))
        {
            break;
        }

        string dummyfiledata;
        size_t filedatalength;
        if (!readStream.Read(&dummyfiledata, 0, filedatalength) || filedatalength != 0)
        {
            break;
        }

        if (offset == -1 && filesize == -1)
        {
            nBreakType = FILE_UPLOAD_SUCCESS;
            break;
        }
    }

    //关闭文件
    fileToSend.close();

    //上传成功
    if (nBreakType == FILE_UPLOAD_SUCCESS)
    {
        Q_EMIT sigTransFileStatus(FILE_UPLOAD_SUCCESS);
        LOG_INFO("Succeed to upload file: %s as there already exist file on server.",fileName.toStdString().c_str());
    }
    //上传失败或者用户取消上传
    else
    {
        if (m_nSendStop)
        {
            LOG_INFO("User canceled to upload file: %s.",fileName.toStdString().c_str());
        }
        else
        {
            //如果不是客户端主动取消发送导致的发送失败，则应该通知界面发送失败
            Q_EMIT sigTransFileStatus(FILE_UPLOAD_FAILED);
            LOG_WARNING("Failed to upload file: %s.",fileName.toStdString().c_str());
        }
    }
}

//下载文件
void ClientFileSocket::SltDownloadFile(QString filePath, QString fileMd5)
{
    // 如果没有连接服务器，重新连接下
    if (!m_pTcpSocket->isOpen()) {
        m_pTcpSocket->connectToHost(MyApp::m_strFileServerAddr,static_cast<quint16>(MyApp::m_nFileSeverPort) );
        if(!m_pTcpSocket->waitForConnected(m_sWaitForTimeout))
        {
            LOG_INFO( "Connect to host %s:%d",
                      m_pTcpSocket->localAddress().toString().toStdString().c_str(),
                      m_pTcpSocket->localPort() );
            Q_EMIT sigTransFileStatus(FILE_DOWNLOAD_FAILED);
            return;
        }
    }

    QFile fileToRecv(filePath);

    if (!fileToRecv.open(QFile::WriteOnly|QFile::Truncate))
    {
        LOG_ERROR( "Open file error : %s",fileToRecv.errorString().toStdString().c_str() );
        Q_EMIT sigTransFileStatus(FILE_DOWNLOAD_FAILED);
        return;
    }

    qint8 nBreakType = FILE_DOWNLOAD_FAILED;
    m_nRecvStop = false;
    QByteArray outblock;
    QByteArray recvBuf;
    qint64 datalength = 0;          //接收数据包的去除包头以后的数据大小
    int dummyoffset = 0;
    while(!m_nRecvStop)
    {
        //cmd = msg_type_download_req, seq = 0, filemd5, offset
        std::string outbuf;
        yt::BinaryWriteStream3 writeStream(&outbuf);
        writeStream.Write(msg_type_download_req);
        writeStream.Write(m_seq);
        writeStream.Write(fileMd5.toStdString().c_str(),static_cast<size_t>( fileMd5.size() ) );
        writeStream.Write(dummyoffset);
        int dummyfilesize = 0;
        writeStream.Write(dummyfilesize);
        string dummyfiledata;
        writeStream.Write(dummyfiledata.c_str(), dummyfiledata.length());
        writeStream.Flush();

        file_msg header = { outbuf.length() };
        outbuf.insert(0, reinterpret_cast<const char*>(&header), sizeof(header));

        if (!SendPackage(outbuf.c_str(), outbuf.length()))
        {
            break;
        }

        recvBuf = recvBuf.mid(static_cast<int>(datalength));      //FIXME:去除包体数据,qint64->int会不会有问题
        //取出一个数据包
        while(true)
        {
            if(!m_pTcpSocket->waitForReadyRead(m_sWaitForTimeout))
            {
                //网络问题
                Q_EMIT sigTransFileStatus(FILE_DOWNLOAD_FAILED);
                return;
            }
            QByteArray buffer = m_pTcpSocket->readAll();
            recvBuf.append(buffer);
            //不够一个包头大小
            if (recvBuf.size() < static_cast<int>(sizeof(file_msg)))
            {
                continue;
            }
            //不够一个整包大小
            file_msg header;
            memcpy(&header, recvBuf.constData(), sizeof(file_msg));
            if (recvBuf.size() < header.packagesize + sizeof(file_msg))
                continue;
            //去除包头
            datalength = header.packagesize;
            recvBuf = recvBuf.mid(sizeof(file_msg));
            break;
        }

        //获取数据内容
        yt::BinaryReadStream2 readStream(recvBuf.constData(),static_cast<size_t>(recvBuf.size()) );
        int cmd;
        if (!readStream.Read(cmd) || cmd != msg_type_download_resp)
        {
            break;
        }

        //int seq;
        if (!readStream.Read(m_seq))
        {
            break;
        }

        //文件md5
        std::string filemd5;
        size_t md5length;
        if (!readStream.Read(&filemd5, 0, md5length) || md5length == 0)
        {
            break;
        }

        //偏移量
        if (!readStream.Read(dummyoffset))
        {
            break;
        }
        //文件大小
        int filesize;
        if (!readStream.Read(filesize))
        {
            break;
        }
        //本次写入文件内容
        std::string filedata;
        size_t filedatalength;
        if (!readStream.Read(&filedata, 0, filedatalength) || filedatalength == 0)
        {
            break;
        }
        //写入文件
        fileToRecv.write(filedata.c_str(),filedata.length());
        //通知界面更改进度条
        dummyoffset += filedata.length();
        if(filesize!=0)
        {
            Q_EMIT sigUpdateTransFileProgress(static_cast<int>((static_cast<qint64>(dummyoffset)* 100) / filesize));
        }

        //文件接收完成
        if (dummyoffset == filesize)
        {
            nBreakType = FILE_DOWNLOAD_SUCCESS;
            break;
        }
    }// end while-loop

    //关闭文件
    fileToRecv.close();

    //下载成功
    if (nBreakType == FILE_DOWNLOAD_SUCCESS)
    {
        Q_EMIT sigTransFileStatus(FILE_DOWNLOAD_SUCCESS);
        LOG_INFO("Succeed to download file: %s",filePath.toStdString().c_str());
    }
    //下载失败或者用户取消下载
    else
    {
        if (m_nRecvStop)
        {
            LOG_INFO("User canceled to download file: %s",filePath.toStdString().c_str());
        }
        else
        {
            Q_EMIT sigTransFileStatus(FILE_DOWNLOAD_FAILED);
            LOG_INFO("Failed to download file: %s",filePath.toStdString().c_str());

        }

        //删除下载的半成品
        fileToRecv.remove();
    }
}

void ClientFileSocket::InitSocket()
{

}

/**
 * @brief ClientSocket::SendPackage 发送数据包
 * @param str       包体
 * @param length    包体长度
 */
bool ClientFileSocket::SendPackage(const char* pBuffer, qint64 length)
{
    Q_ASSERT(pBuffer!=nullptr && length>0);

    int nSentBytes = 0;
    qint64 nRet = 0;
    do
    {
        nRet = m_pTcpSocket->write(pBuffer,length);
        if(nRet == -1)
        {
            //一旦出现错误就立刻关闭Socket
            LOG_ERROR("Send data error, disconnect server:%s, port:%d.",
                      MyApp::m_strFileServerAddr.toStdString().c_str(),
                      MyApp::m_nFileSeverPort);
            CloseFileServerConnection();
            return false;
        }

        nSentBytes += nRet;

        if(nSentBytes>=length)
            break;
        myHelper::Sleep(1000);
    } while (true);
    return true;
}

bool ClientFileSocket::ReadPackage(QByteArray& buffer, qint64 nSize)
{
    Q_ASSERT(nSize>0);

    int nRet = 0;
    qint64 nRecvBytes = 0;
    do
    {
        qDebug() << "ClientFileSocket::ReadPackage : " << m_pTcpSocket->waitForReadyRead(50000);
        QByteArray tmpBuf = m_pTcpSocket->read(nSize-nRecvBytes);
        nRet = tmpBuf.length();
        if(nRet < 1)
        {
            qDebug() << "Recv data error, disconnect server";
            CloseFileServerConnection();
            return FALSE;
        }
        buffer.append(tmpBuf);
        nRecvBytes += nRet;
        if(nRecvBytes >= nSize)
            break;
        ::Sleep(1);

    } while (true);
    return true;
}

bool ClientFileSocket::ReadPackage(char* pBuffer, qint64 nSize)
{
    Q_ASSERT(pBuffer!=nullptr && nSize>0);

    qint64 nRet = 0;
    qint64 nRecvBytes = 0;
    do
    {
        qDebug() << "ClientFileSocket::ReadPackage : " << m_pTcpSocket->waitForReadyRead(50000);
        nRet = m_pTcpSocket->read(pBuffer+nRecvBytes,nSize-nRecvBytes);
        if(nRet < 1)
        {
            qDebug() << "Recv data error, disconnect server";
            CloseFileServerConnection();
            return false;
        }

        nRecvBytes += nRet;
        if(nRecvBytes >= nSize)
            break;

        ::Sleep(1);

    } while (true);

    return true;
}
